<?php
/**
 * KePHP, Keep PHP easy!
 *
 * @license   https://opensource.org/licenses/MIT
 * @copyright Copyright 2015-2018 KePHP Authors All Rights Reserved
 * @link      http://kephp.com/utils ( https://git.oschina.net/kephp/kephp-utils )
 * @author    曾建凯 <janpoem@163.com>
 */

namespace Ke\TestUtils;

/**
 * 测试方法实例，以 ReflectionMethod ，来传递需要被测试的方法。
 *
 * @package Ke\Utils\Test
 */
class TestMethod
{
	
	/** @var null|string|object 方法的主体，可以是静态类、对象，或为空 */
	private $object = null;
	
	/** @var \ReflectionMethod */
	private $refMethod = null;
	
	/** @var array */
	private $exceptions = [];
	
	/**
	 * TestMethod 构造函数
	 *
	 * @param null                   $object
	 * @param \ReflectionMethod|null $method
	 * @param \Throwable|null        $throwable
	 */
	public function __construct($object = null, \ReflectionMethod $method = null, \Throwable $throwable = null)
	{
		$this->object = $object;
		$this->refMethod = $method;
		if (isset($throwable))
			$this->addException($throwable);
	}
	
	/**
	 * 该测试方法是否可被调用
	 *
	 * @return bool
	 */
	public function isInvokable()
	{
		return isset($this->refMethod) && $this->refMethod instanceof \ReflectionMethod;
	}
	
	/**
	 * 该测试方法是否有异常状态
	 *
	 * @return bool
	 */
	public function hasExceptions()
	{
		return !empty($this->exceptions);
	}
	
	/**
	 * 添加一个异常
	 *
	 * @param \Throwable $throwable
	 * @return $this
	 */
	public function addException(\Throwable $throwable)
	{
		$this->exceptions[] = $throwable;
		return $this;
	}
	
	/**
	 * 如果存在异常，则抛出最后一个异常
	 */
	public function throwLastException()
	{
		if ($this->hasExceptions()) {
			throw $this->exceptions[count($this->exceptions) - 1];
		}
	}
	
	/**
	 * 取回测试方法的所有异常
	 *
	 * @return array
	 */
	public function getExceptions(): array
	{
		return $this->exceptions;
	}
	
	/**
	 * 生成一个该测试方法的TestItem。
	 *
	 * @param mixed       $excepted
	 * @param mixed       $args
	 * @param string|null $message
	 * @return TestItem
	 */
	public function newTestItem($excepted, $args = null, string $message = null): TestItem
	{
		return TestItem::factory($excepted, $args, $message)->setMethod($this);
	}
	
	/**
	 * 批量生成测试用例
	 *
	 * $filterItemCallback($obj, $index, $item)
	 * - $obj   - 该测试方法的主体（对象或类），实际写回调函数时，为了方便产生代码提示，可以自行在参数前补充类名。
	 * - $index - 对应的$item的索引（键名）
	 * - $item  - 测试用例（TestItem）实例，与 $obj 一样，可以自行补充类名，以提供代码提示。
	 *
	 * ```php
	 * $testMethod->newTestItems([], function(AnyClass $obj, $index, TestItem $item) {
	 *     $item->excepted = mb_strtolower($item->excepted);
	 *     if ($index === 2) {
	 *         // ...
	 *     }
	 * });
	 * ```
	 *
	 * @param array                  $items              多个测试用例的数组
	 * @param callable|\Closure|null $filterItemCallback 过滤每一个测试用例的回调函数
	 * @return array
	 */
	public function newTestItems(array $items, $filterItemCallback = null)
	{
		if (isset($filterItemCallback) && !is_callable($filterItemCallback))
			$filterItemCallback = null;
		
		$results = [];
		foreach ($items as $index => $item) {
			if (!is_array($item))
				$item = [$item];
			$item = $this->newTestItem(...$item);
			if (isset($filterItemCallback)) {
				$filterItemCallback($this->object, $index, $item);
			}
			$results[$index] = $item;
		}
		
		return $results;
	}
	
	/**
	 * 调用该测试方法
	 *
	 * @param array|null $args
	 * @return mixed
	 */
	public function invoke(...$args)
	{
		if ($this->isInvokable()) {
			return $this->refMethod->invokeArgs($this->object, $args ?? []);
		}
		return null;
		// throw new TestMethodException('Invalid test method, cannot be invoked!');
		// 暂时不抛出异常
	}
}